/*****************************************************************/
//Display monitors what is shown on the screen. It does the map, enemies,
//the player, what screen is showing, etc
// Tyler Wozniak & Andrew Hall
/**************************************************************/


#include "Display.h"
#include "GameManager.h"
using namespace std;



//Constructor for display - creates the base display array
Display::Display(GameManager& game)
{

	gameRef = &game;
	displayArray = new char*[MAX_HEIGHT];
	for(int i = 0; i < MAX_HEIGHT; i++)
	{
		displayArray[i] = new char[MAX_WIDTH];
	}
	currentMessages = new Queue<string>();
	initColors();
	//And initialize the menuOps because idk where else to do this
	menuOps.push_back("> Tank - You are slow to move and attack, but you can take a beating");
	menuOps.push_back("  Damage - You try to deal as much damage as possible each hit");
	menuOps.push_back("  Speed - You hit fast and often, though not as hard.");
}

//Sets up all the colors for different characters to be drawn to the display
//Effectively, sets up a sort of hash table for colors vs characters
void Display::initColors()
{
	//Initialize so we have 256 values;
	for(int i = 0; i < 256; i++)
		displayCharColors[char(i)] = 0;

	//So create a function object which can set the colors
	dispFctor Fctor(this);
	for_each(displayCharColors.begin(),displayCharColors.end(),Fctor);

	displayCharColors[HERO] = BROWN;
	displayCharColors[FLOOR] = BLUE;
	displayCharColors['P'] = MAGENTA;
	//displayCharColors[' '] = BLACK;
}

//Draws the display array to the console
void Display::drawDisplayArray(Map& curMap)
{
	system("cls");
	string uiArray[MAX_HEIGHT] = { }; 
	string* uiPtr = UItoArray(uiArray,*(gameRef->currentPlayer)); //Make an array of strings amd then make the UI from it
	for(int i = 0; i < MAX_HEIGHT; i++)
	{
		//So draw one row
		drawDisplayArrayRow(i,curMap);
		setuiColor(i); // Determine the row color for the UI
		cout << uiPtr[i]; //And draw the UI row
		cout << endl;
	}
	setColor(LIGHTGREY);
}

//Displays a single row of the display array
void Display::drawDisplayArrayRow(int row,Map& curMap)
{
	for(int i = 0; i < MAIN_WIDTH; i++)
	{
		//So, if the things being drawn are in line of sight, they will be brighter!
		int mapRow = row + rowConv;
		int mapCol = i + colConv;

		if(mapRow >= 0 && mapRow < MAX_MAP && mapCol >= 0 && mapCol <= MAX_MAP)
		{
			if(curMap.getMapPoint(mapRow,mapCol).visible)
			{
				//Now what color to draw!?
				int lighterColor;
				if(displayCharColors[displayArray[row][i]] == DARKGREY)
				{
					lighterColor = LIGHTGREY;
				}
				else
				{
					lighterColor = displayCharColors[displayArray[row][i]] + 8;
				}
				setColor(lighterColor);
			}
			else
			{
				//If not, they will be darker!
				setColor(displayCharColors[displayArray[row][i]]);
			}
		}
		else
		{
			//If not, they will be darker!
			setColor(displayCharColors[displayArray[row][i]]);
		}
		cout << displayArray[row][i];
	}
}

//Resets the displayArray to blank values
void Display::cleanDisplayArray()
{
	for(int i = 0; i < MAX_HEIGHT; i++)
	{
		for(int j = 0; j < MAX_WIDTH; j++)
		{
			displayArray[i][j] = ' ';
		}
	}
	displayArray[HERO_R][HERO_C] = 'H';
}

//Sets the color to whatever is appropriate for the current row of the UI
void Display::setuiColor( int i )
{
	switch(i)
	{
	case 1:
		setColor(YELLOW);
		break;
	case 3:
		if(gameRef->currentPlayer->currentHealth() < gameRef->currentPlayer->maxHealth()/4)
		{
			setColor(LIGHTRED);
		}
		else if(gameRef->currentPlayer->currentHealth() < gameRef->currentPlayer->maxHealth()/2)
		{
			setColor(YELLOW);
		}
		else
			setColor(WHITE);
		break;
	case 4:
		setColor(LIGHTGREEN);
		break;
	case 6:
	case 7:
	case 8:
		setColor(BROWN);
		break;
	default:
		setColor(WHITE);
	}
}

//Will take in a reference to the hero and display
//the UI on the right side of the screen
string* Display::UItoArray( string uiArr[], const Player& curP)
{
	//Now we place  the appropriate string data in the array, line by line
	uiArr[1] = curP.getName();
	uiArr[1] += "    ";
	uiArr[1] += "Turn:";
	uiArr[1] += intToString(gameRef->curTurn);

	string hp = " BYTES: ";
	hp += intToString(curP.currentHealth());
	hp += "/";
	hp += intToString(curP.maxHealth());
	uiArr[3] = hp;

	string hpBar = " ";
	int numPips = 20 * ((double)curP.currentHealth() / (double)curP.maxHealth());
	for(int i = 0; i < numPips; i++)
		hpBar += char(254);
	for(int i = 0; i < (20 - numPips); i++)
		hpBar += '-';
	uiArr[4] = hpBar;

	//Note: the following algorithm will draw the stats at even spaces
	string atkAndPow;
	atkAndPow = " Hit: ";
	atkAndPow += intToString(curP.getHit());
	while(atkAndPow.length() < 11)
		atkAndPow += " ";
	atkAndPow += " Pow: ";
	atkAndPow += intToString(curP.getStr());
	uiArr[6] = atkAndPow;

	string defAndEff;
	defAndEff = " Eva: ";
	defAndEff += intToString(curP.getEva());
	while(defAndEff.length() < 11)
		defAndEff += " ";
	defAndEff += " Eff: ";
	defAndEff += intToString(curP.getAgi());
	uiArr[7] = defAndEff;

	string spcAndMem;
	spcAndMem = "  ";
	while(spcAndMem.length() < 11)
		spcAndMem += " ";
	spcAndMem += " Mem: ";
	spcAndMem += intToString(curP.getSta());
	uiArr[8] = spcAndMem;

	string helpTitle = " Key References:";
	uiArr[10] = helpTitle;

	string help1 = " Q: Quit";
	while(help1.length() < 13)
		help1 += " ";
	help1 += " r t y";
	uiArr[12] = help1;

	string help2 = " M: Messages";
	while(help2.length() < 13)
		help2 += " ";
	help2 += "  \\|/";
	uiArr[13] = help2;

	string help3 = " Spc: Stand";
	while(help3.length() < 13)
		help3 += " ";
	help3 += " f-@-h";
	uiArr[14] = help3;

	string help4;
	while(help4.length() < 13)
		help4 += " ";
	help4 += "  /|\\";
	uiArr[15] = help4;

	string help5;
	while(help5.length() < 13)
		help5 += " ";
	help5 += " v g b";
	uiArr[16] = help5;

	return uiArr;
}

//Will take the current map and place the tiles on the draw array
void Display::mapToDisplay(Map& curMap, int heroRow, int heroCol)
{
	//We make two conversion numbers for the difference between the world x y and the
	//drawn x y
	curMap.updateVisible(heroRow,heroCol); //Update our visibles with our current hero position

	//Updat the row to display conversions
	rowConv = heroRow - HERO_R;
	colConv = heroCol - HERO_C; 

	for(int i = 0; i < MAX_HEIGHT; i++)
	{
		for(int j = 0; j < MAX_WIDTH; j++)
		{
			if(i + rowConv < 0 || i + rowConv >= MAX_MAP || j + colConv < 0 || j + colConv >= MAX_MAP)
			{
				//So if the position is out of bounds of the map, just draw it black
				displayArray[i][j] = ' ';
			}
			else
			{
				//Otherwise, if it's seen, we add it
				if(curMap.getMapPoint(i + rowConv,j + colConv).seen)
				{
					displayArray[i][j] = char(curMap.getMapPoint(i + rowConv,j + colConv));

					if(curMap.getMapPoint(i + rowConv,j + colConv).visible)
					{
						//And then if it's visible, we see if there are any Enemies there
						int posIndex = curMap.checkPosition(i + rowConv,j + colConv);
						if(posIndex >= 0)
						{
							Enemy* enemyPtr = curMap.getEnemy(posIndex);
							char enChar = char(*enemyPtr);
							if(!(enemyPtr->getSeen()))
							{
								//Push the message of seeing the first seen enemy onto the queue
								string outstring = "A ";
								outstring += enemyPtr->getName();
								outstring += " comes into view.";
								addPlayerMessage(outstring); 
							}
							displayArray[i][j] = enChar;
							enemyPtr->Seen();
						}
					}
				}
				else
				{
					displayArray[i][j] = ' ';
				}
			}
		}
	}

	displayArray[HERO_R][HERO_C] = HERO; //Then draw the damn hero
}

//Adds to the player messages for the turn
void Display::addPlayerMessage(string s)
{
	if (currentMessages->Length() >= MAX_PLAYER_MESSAGES)
	{
		string temp = (*currentMessages)--;
	}
	*(currentMessages) += s;
	allMessages.push(s);
}

//Shows the player messages that have accumulated this turn, then gets rid of them
void Display::showPlayerMessages(void)
{
	cout << "Player current messages: " << endl;
	while ( currentMessages->Length() != 0)
		cout << (*currentMessages)-- << endl;
}

//Shows all messages
//Also removes any duplicate messages given close together
void Display::showAllMessages(void)
{
	queue<string> temp;
	vector<string> tempVec;
	cout << "All stored messages: " << endl;
	while (!allMessages.empty())
	{
		//Pass them into a vector so we can iterate on tehm
		tempVec.push_back(allMessages.front());
		allMessages.pop();
	}
	vector<string>::iterator newIt = unique(tempVec.begin(),tempVec.end());
	tempVec.resize( newIt - tempVec.begin() ); 
	//Declare the foreach functors
	printStrings strPrinter;
	stringToQueue strToQFctor(allMessages);
	for_each(tempVec.begin(),tempVec.end(),strPrinter);
	for_each(tempVec.begin(),tempVec.end(),strToQFctor);
}


//Debugging tool. Pauses until you read an error message
//Useful in case error appears before a console clear
void Display::printErrorMessage( string out )
{
	setColor(LIGHTRED);
	cout << "Error: " << out << endl;
	system("pause");
	setColor(LIGHTGREY);
}

//Draws the current screen based on the current state
void Display::drawCurrentScreen(void)
{
	switch(gameRef->curState)
	{
		case TITLE:
			printTitle();
			break;
		case MENU:
			printMenu();
			break;
		case GAME:
			cleanDisplayArray();
			mapToDisplay(*(gameRef->currMap), gameRef->currentPlayer->getY(), gameRef->currentPlayer->getX());
			drawDisplayArray(*(gameRef->currMap));
			showPlayerMessages();
			break;
		case MESSAGES:
			system("CLS");
			showAllMessages();
			break;
		default:
			break;
	}
}

//Destructor
Display::~Display(void)
{
	for(int i = 0; i < MAX_HEIGHT; i++)
	{
		delete displayArray[i];
	}
	delete displayArray;
}

//Helper method to print out the tile screen
//Title fonts from: http://www.network-science.de/ascii/
void Display::printTitle()
{
	system("cls");
	string titleString = "                                                    \n\n\n\n";
	titleString	+= "                                      ___                   _ \n";
	titleString	+= "            /\\  /\\___  __ _ _ __     / __\\ __ __ ___      _| |\n";
	titleString	+= "           / /_/ / _ \\/ _` | '_ \\   / / | '__/ _` \\ \\ /\\ / / |\n";
	titleString	+= "          / __  /  __/ (_| | |_) | / /__| | | (_| |\\ V  V /| |\n";
	titleString	+= "          \\/ /_/ \\___|\\__,_| .__/  \\____/_|  \\__,_| \\_/\\_/ |_|\n";
	titleString	+= "                           |_|                                \n";
	titleString	+= "          				                                    \n";
	string titleString2	= "                                                              \n";
	titleString2	+= "             __           __                    __            \n";
	titleString2	+= "            |__)_ _ _ _  (_  _  _  _ _  |_ _   (_ |_ _  _|_   \n";
	titleString2	+= "            |  | (-_)_)  __)|_)(_|(_(-  |_(_)  __)|_(_|| |_   \n";
	titleString2	+= "                            |                                \n\n";
	titleString2	+= "		   By Tyler Wozniak & Andrew Hall\n";
	setColor(YELLOW);
	cout << titleString;
	setColor(BROWN);
	cout << titleString2 << endl;
	setColor(LIGHTGREY);
}

//Changes which menu option is selected based on input
void Display::setMenuOp( char in )
{
	//Declare our fnctor
	findStringPart finder(">");
	vector<string>::iterator currentOp = find_if(menuOps.begin(),menuOps.end(),finder);
	if(in == 'w' && currentOp != menuOps.begin())
	{
		(*currentOp)[0] = ' ';
		(*(currentOp - 1))[0] = '>';
	}
	else if(in == 's' && *currentOp != menuOps.back())
	{
		(*currentOp)[0] = ' ';
		(*(currentOp + 1))[0] = '>';
	}
}

//Prints out the menu screen
void Display::printMenu()
{
	system("cls");
	setColor(LIGHTRED);
	cout << "Welcome to Heap Crawl." << endl;
	setColor(LIGHTGREY);
	cout << "An evil wizard named New has gone around allocating rogue memory," << endl;
	cout << "and now the Heap is a mess! You must make your way down the heap," << endl;
	cout << "as our hero, Null Ptr and save the day!\n" << endl;

	setColor(YELLOW);
	cout << "\n\nChoose Your Role:\n" << endl;
	
	setColor(WHITE);
	//Print out each menu option from the vector using for_each
	printStrings printer;
	for_each(menuOps.begin(),menuOps.end(),printer);

	setColor(DARKGREY);
	cout << "\n\nUse the W and S keys to move between options" << endl;
	cout << "Press space to choose your role type" << endl;
	cout << "Use Q to quit the game, or exit a submenu during play" << endl;
	cout << "Any further controls will be shown on the screen" << endl;
	setColor(LIGHTGREY);
}

//Helper function to reduce codereuse
void Display::setColor( int colorEnum )
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), colorEnum);
}

//Returns tank, damage, or speed based on current menu op
string Display::getMenuOp()
{
	findStringPart finder(">");
	vector<string>::iterator currentOp = find_if(menuOps.begin(),menuOps.end(),finder);
	if(currentOp == menuOps.begin())
	{
		return "tank";
	}
	else if(*currentOp == menuOps.back())
	{
		return "speed";
	}
	else
	{
		return "damage";
	}
}

//Print the credits screen
//font from http://www.network-science.de/ascii/
void Display::printCredits()
{
	system("cls");
	if(gameRef->gameWon)
	{
		cout << "Well done, brave Null Ptr! You have defeated the evil wizard New, and his minions!" << endl;
		cout << "The kingdom of Heap will be safe..." << endl;
		setColor(LIGHTRED);
		cout << "...for now..." << endl;
		setColor(LIGHTGREY);
	}
	else
	{
		setColor(WHITE);
		cout << "I am sorry...but you were not strong enough to survive the Heap..." << endl;
		setColor(LIGHTGREY);
		cout << "The memory will forever be in disarray (pun intended)! For shame..." << endl;
		setColor(DARKGREY);
		cout << "Better luck next time..." << endl;
		setColor(LIGHTGREY);
	}
	system("pause");
	system("cls");
	setColor(LIGHTRED);
	cout <<  " _______ _                 _                ___            \n";
	cout <<  "(_______) |               | |              / __)           \n";
	cout <<  "    _   | |__  _____ ____ | |  _  ___    _| |__ ___   ____ \n";
	setColor(WHITE);
	cout <<  "   | |  |  _ \\(____ |  _ \\| |_/ )/___)  (_   __) _ \\ / ___)\n";
	cout <<  "   | |  | | | / ___ | | | |  _ (|___ |    | | | |_| | |    \n";
	setColor(LIGHTRED);
	cout <<  "   |_|  |_| |_\\_____|_| |_|_| \\_|___/     |_|  \\___/|_|    \n";
	cout <<  "                                                           \n";
	cout <<  "        ______  _              _             _ \n";
	cout <<  "       (_____ \\| |            (_)           | |\n";
	cout <<  "        _____) ) | _____ _   _ _ ____   ____| |\n";
	setColor(WHITE);
	cout <<  "       |  ____/| |(____ | | | | |  _ \\ / _  |_|\n";
	cout <<  "       | |     | |/ ___ | |_| | | | | ( (_| |_ \n";
	setColor(LIGHTRED);
	cout <<  "       |_|      \\_)_____|\\__  |_|_| |_|\\___ |_|\n";
	cout <<  "                        (____/        (_____|  \n";

	setColor(LIGHTGREY);
}

